/* Este seria el fuente donde se iniciaria todo */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "traductor.h"


FILE *archivo_de_salida;
// es global para que las otras funciones le vean

extern int traducir;
extern int yylineno;
extern int scope;

extern struct list_head *tablaVariables[MAX_VISIBILIDAD];
extern struct list_head *tablaFunciones;
extern struct list_head *listaConstantes;

extern struct variable_por_funcion variables_por_funcion[CANTIDAD_VARIABLES];

void imprimirTexto(char *texto){
    fprintf(archivo_de_salida,texto);
    fprintf(stderr, "(FILE archivo_de_salida:) %s", texto);
}


void
inicializar_vpf ()
{
    int i;
    
    for (i = 0; i < CANTIDAD_VARIABLES; i++) {
        variables_por_funcion[i].tipo = INICIO_TIPO_VPF;
        variables_por_funcion[i].funcion = INICIO_TIPO_FUNCION;
    }
}

void imprimirBuffer(struct textoImpresion *listaTexto){
    struct textoImpresion *t=NULL;
    
    if(listaTexto!=NULL){
        t = list_entry(&listaTexto->lista, struct textoImpresion, lista);
            imprimirTexto(t->texto);
        list_for_each_entry(t,&listaTexto->lista,lista){
                imprimirTexto(t->texto);
        }
    }
}

void imprimirPantalla(struct textoImpresion *listaTexto){
    struct textoImpresion *t=NULL;
    
    if(listaTexto!=NULL){
        t = list_entry(&listaTexto->lista, struct textoImpresion, lista);
            printf("%s",t->texto);
        list_for_each_entry(t,&listaTexto->lista,lista){
                printf("%s",t->texto);
        }
        printf("\n");
    }
}

struct textoImpresion *agregarTextoAntes(char *texto,struct textoImpresion *listaTexto){
    struct textoImpresion *t=NULL;
    
    fprintf(stderr, "agregarTextoAntes: %s\n", texto);
    if((t=(struct textoImpresion*)malloc(sizeof(struct textoImpresion)))==NULL)
        errorFatal("No hay mas memoria");
    
    INIT_LIST_HEAD(&t->lista);
    t->texto=strdup(texto);
    
    if(listaTexto == NULL){
        listaTexto = t;
        return t;
    }
    
    list_add(&t->lista,&listaTexto->lista);
    return t;
}

void agregarTextoDespues(char *texto,struct textoImpresion *listaTexto){
    struct textoImpresion *t=NULL;

    fprintf(stderr, "agregarTextoDespues: %s\n", texto);
    if((t=(struct textoImpresion*)malloc(sizeof(struct textoImpresion)))==NULL)
        errorFatal("No hay mas memoria");
    
    INIT_LIST_HEAD(&t->lista);
    t->texto=strdup(texto);
    
    list_add_tail(&t->lista,&listaTexto->lista);
}

void agregarLista(struct textoImpresion *cabeza,struct textoImpresion *resto){
    struct list_head *a = &cabeza->lista;
    struct list_head *f = &resto->lista;
    struct list_head *ap = a->prev;
    struct list_head *fp = f->prev;
    
    ap->next = f;
    f->prev = ap;
    a->prev = fp;
    fp->next = a;
    
}

struct id* crearNodoId(void){
    
    struct id *v=NULL;
    if((v=(struct id*)malloc(sizeof(struct id)))==NULL)
        errorFatal("No hay mas memoria");

    v->buffer=NULL;        
    
    return v;
}

int verificaMain(void){
    struct funcion *f = buscarNodoFuncion("main");
    
    return (f && !f->prototipo);

}

struct variable *buscaVariableScope(char *s,int scope){
    
    struct variable *v=NULL;
    
    if(tablaVariables[scope] == NULL)
        return NULL;
        
    v = list_entry(tablaVariables[scope], struct variable, lista);
    if(strcmp(s,v->lexema) == 0){
            return v;
    }
    
    list_for_each_entry(v,tablaVariables[scope],lista) {
        if(strcmp(s,v->lexema) == 0){
            return v;
        }
    }
    
    return NULL;
    
}

struct variable *buscaVariable(char *s){
    int scopeActual = scope;
    struct variable *v;
    
    while(scopeActual>0){
        v = buscaVariableScope(s,scopeActual);
        if(v!=NULL)
            return v;
        scopeActual--;
    };
    
    v = buscaVariableScope(s,scopeActual);
    if(v!=NULL)
        return v;
    
    return NULL;
}

void agregarDeclaracion(struct list_head *nuevo,struct list_head *lista){
    
    list_add(nuevo,lista);
}

void asignarDeclaradosTipo(int tipo,struct list_head *lista){
    struct variable *v;

    v = list_entry(lista,struct variable,listaDeclarados);
        v->tipo = tipo;
    
    list_for_each_entry(v,lista,listaDeclarados)
        v->tipo = tipo;
    
}


void
imprimirvariablesdelafuncion(int nro)
{
    int i;
    char bandera = 0;
    
    for (i = 0; i < CANTIDAD_VARIABLES; i++)
        if ((variables_por_funcion[i].funcion == nro)) {
            bandera = 1;
            break;
        }
            
    if (!bandera) return;
    
    imprimirTexto("var\n\0");
    for (i = 0; i < CANTIDAD_VARIABLES; i++)
        if (variables_por_funcion[i].funcion == nro) {
            imprimirTexto(variables_por_funcion[i].nombre);
            imprimirTexto(" : \0");
            switch (variables_por_funcion[i].tipo) {
                case ENTERO:
                    imprimirTexto("Integer;\n\0");
                    break;
                case CARACTER:
                    imprimirTexto("Char;\n\0");
                    break;
                case REAL:
                    imprimirTexto("Real;\n\0");
                    break;
            }
        }
    
}

void imprimirFuncion(struct funcion *f, int nrofuncion){
    int j=0;
    struct argumento *v;
    
    imprimirTexto("\n\0");
    imprimirTexto("function\0");
    imprimirTexto(" ");
    imprimirTexto(f->lexema);
    imprimirTexto("(\0");
    
    v = list_entry(&f->listaArgumentos->args->lista,struct argumento,lista);
        if(v->tipo != TIPO_VACIO){
            j++;
            // imprimirTexto("$\0");
            imprimirTexto(v->lexema);
            imprimirTexto(": \0");
            switch (v->tipo) {
                case ENTERO:
                    imprimirTexto("Integer\0");
                    break;
                case CARACTER:
                    imprimirTexto("Char\0");
                    break;
                case REAL:
                    imprimirTexto("Real\0");
                    break;
            }
            imprimirTexto("; \0");
        }
    
    list_for_each_entry(v,&f->listaArgumentos->args->lista,lista){
        if(v->tipo != TIPO_VACIO){
            j++;
            // imprimirTexto("$\0");
            imprimirTexto(v->lexema);
            imprimirTexto(": \0");
            switch (v->tipo) {
                case ENTERO:
                    imprimirTexto("Integer \0");
                    break;
                case CARACTER:
                    imprimirTexto("Char \0");
                    break;
                case REAL:
                    imprimirTexto("Real \0");
                    break;
            }
            if (list_entry(v->lista.next, typeof(*v), lista) != &f->listaArgumentos->args->lista)
                imprimirTexto("; \0");
        }
    }
    
    if(j>0)
        fseek(archivo_de_salida,-1,SEEK_CUR);
    imprimirTexto("):\0");
    switch (f->retorno) {
        case ENTERO:
            imprimirTexto("Integer;\0");
            break;
        case CARACTER:
            imprimirTexto("Char;\0");
            break;
        case REAL:
            imprimirTexto("Real;\0");
            break;
    }
    imprimirTexto("\n\0");
    imprimirvariablesdelafuncion(nrofuncion);
}


int compararArgumentos(struct argumentos *real, struct argumentos *llamada,char *lexema){
    struct list_head *listaReal;
    struct list_head *listaLlamada;
    int i=0;
    
    struct argumento *tmpReal;
    struct argumento *tmpLlamada;
    
    if(real->cArg != llamada->cArg){
        errorSemantico(lexema, " el numero de argumentos incorrecto");
        return 0;
    }
    
    listaReal= &real->args->lista;
    listaLlamada = &llamada->args->lista;
    
    tmpReal = list_entry(listaReal,struct argumento,lista);
    tmpLlamada = list_entry(listaLlamada,struct argumento,lista);
        
    if(tmpReal->tipo != tmpLlamada->tipo || tmpReal->posicion != tmpLlamada->posicion){
        errorSemantico(lexema," el tipo de los argumentos es invalido\n");
        return 0;
    }
    
    for(i;i<real->cArg;i++){
        listaReal = listaReal->next;
        listaLlamada = listaLlamada->next;
    
        tmpReal = list_entry(listaReal,struct argumento,lista);
        tmpLlamada = list_entry(listaLlamada,struct argumento,lista);
            
        if(tmpReal->tipo != tmpLlamada->tipo || tmpReal->posicion != tmpLlamada->posicion){
            errorSemantico(lexema," el tipo de los argumentos es invalido\n");
            return 0;
        }
    }
    return 1;
}

void agregarVariable(struct variable *v){
    
    
    
    if(tablaVariables[scope] == NULL){
        tablaVariables[scope] = &v->lista;
        return;
    }
    
    list_add(&v->lista,tablaVariables[scope]);
}

void agregarNodoArgumento(int tipo,char *lexema,struct argumentos *a){
    
    struct argumento *arg=NULL;
    
    if((arg=(struct argumento*)malloc(sizeof(struct argumento)))==NULL)
        errorFatal("No hay mas memoria");
    
    INIT_LIST_HEAD(&arg->lista);

    arg->tipo = tipo;
    arg->posicion = a->cArg;
    arg->lexema = strdup(lexema);
    
    if(tipo != TIPO_VACIO)
        a->cArg++;
    
    if(a->args == NULL){
        a->args = arg;
        return;
    }
    
    list_add_tail(&arg->lista,&a->args->lista);
}


/**
void agregarNodoVariableporFuncion(int tipo,char *nombre, int compuesto, int funcion, struct variable_por_funcion *a){
    
    struct variable_por_funcion *vpf=NULL;
    
    if((vpf=(struct variable_por_funcion*)malloc(sizeof(struct variable_por_funcion)))==NULL)
        errorFatal("No hay mas memoria");
    
    INIT_LIST_HEAD(&vpf->lista);

    vpf->tipo = tipo;
    strcpy(vpf->nombre, nombre);
    vpf->compuesto = compuesto;
    vpf->funcion = funcion;
    
    list_add_tail(&vpf->lista,&a->lista);
}
*/


struct argumentos* crearNodoArgumentos(void){
    
    struct argumentos *v=NULL;
    if((v=(struct argumentos*)malloc(sizeof(struct argumentos)))==NULL)
        errorFatal("No hay mas memoria");
    
    v->args=NULL;
    v->cArg=0;
    v->buffer=NULL;
    return v;
}

struct funcion* crearNodoFuncion(int retorno, char *lexema, struct argumentos *args){
    
    struct funcion *f=NULL;
    if((f=(struct funcion*)malloc(sizeof(struct funcion)))==NULL)
        errorFatal("No hay mas memoria");
    
    INIT_LIST_HEAD(&f->lista);    
    
    f->retorno = retorno;
    f->lexema = strdup(lexema);
    f->listaArgumentos = args;
    
    return f;
}

void agregarNodoFuncion(struct funcion *f){
    
    if(tablaFunciones == NULL){
        tablaFunciones = &f->lista;
        return;
    }
    
    list_add(&f->lista,tablaFunciones);
}

struct funcion *buscarNodoFuncion(char *s){
    struct funcion *f=NULL;
    
    if(tablaFunciones == NULL)
        return NULL;
        
    f = list_entry(tablaFunciones, struct funcion, lista);
    if(strcmp(s,f->lexema) == 0){
            return f;
    }
    
    list_for_each_entry(f,tablaFunciones,lista) {
        if(strcmp(s,f->lexema) == 0){
            return f;
        }
    }
    
    return NULL;
    
}

struct funcion *buscarNodoFuncionR(char *s){
    struct funcion *f=NULL;
    
    if(tablaFunciones == NULL)
        return NULL;
        
    f = list_entry(tablaFunciones, struct funcion, lista);
    if(strcmp(s,f->lexema) == 0 && !f->prototipo){
            return f;
    }
    
    list_for_each_entry(f,tablaFunciones,lista) {
        if(strcmp(s,f->lexema) == 0){
            return f;
        }
    }
    
    return NULL;
    
}

struct variable* crearNodoVariable(void){
    
    struct variable *v=NULL;
    if((v=(struct variable*)malloc(sizeof(struct variable)))==NULL)
        errorFatal("No hay mas memoria");
    
    INIT_LIST_HEAD(&v->lista);    
    INIT_LIST_HEAD(&v->listaDeclarados);
    return v;
}

void incrementarScope(){
    if(scope < MAX_VISIBILIDAD) 
        scope++;
        
}

void decrementarScope(){
    if(scope > 0){
        tablaVariables[scope] = NULL;
        scope--;
    }
    
}

void errorFatal(char *msj)
{
    printf("%s\n",msj);
    exit(EXIT_FAILURE);
}


void errorSemantico(char *lexema,char *msj)
{
       traducir = 0;
    
    printf("Error Semantico: cerca de la linea %d: %s %s\n", yylineno, lexema, 
        msj);
}



char *
nombre_archivo_salida(char *nombre)
{
    int len;
    char *nombre_de_salida;

    len = strlen(nombre);
    nombre_de_salida = malloc(sizeof(char) * (len + 3));
    strncpy(nombre_de_salida, nombre, len - 1);
    strcat(nombre_de_salida, "pas");
    
    return (nombre_de_salida);
}

void
escribir_inicio_programa(char *nombre)
{
    int len;
    char *nombre_del_programa;
    
    len = strlen(nombre);
    nombre_del_programa = malloc((len - 4) * sizeof(char));
    strncpy(nombre_del_programa, nombre, len - 4);
    fprintf(archivo_de_salida, "program %s;\n", nombre_del_programa);
    free(nombre_del_programa);
}

int
main (int argc, char **argv)
{
    /* Variables */
    FILE *archivo_de_entrada;
    char *salida;

    /* Aqui preguntamos si nos paso el archivo a compilar */
    if (argc < 2) {
        printf("Uso: ./traductor archivo-fuente.c\n");
        return (1);
    }

    /* Abrimos el archivo para que lo lea el analizador lexico */
    if ((archivo_de_entrada = fopen(argv[1], "r")) == NULL) {
        printf("No se pudo abrir el archivo fuente.\n");
        return (-1);
    } // si no entro en este if quiere decir que abrio bien el archivo
    cambiar_yyin(archivo_de_entrada);
    
    /* Abrimos el archivo de salida */
    salida = nombre_archivo_salida(argv[1]);
    if ((archivo_de_salida = fopen(salida, "w")) == NULL) {
        printf("No se pudo crear el archivo de salida.\n");
        fclose(archivo_de_entrada);
        return (-1);
    }
    
    escribir_inicio_programa(salida);
    
    /* Empezamos la traduccion */
    inicializar_vpf();
    yyparse();

    if(traducir) {
        fprintf(archivo_de_salida,"\nbegin\n\twriteLn(main());\nend.\n");        printf("\nTraduccion exitosa. Salida: %s.\n", salida);
        fclose(archivo_de_salida);
        fclose(archivo_de_entrada);
        free(salida); //liberamos el espacio del nombre del archivo de salida
		return 0;
   }

    fclose(archivo_de_salida);
    fclose(archivo_de_entrada);

    printf("La traduccion no se pudo realizar.\n");
    free(salida); //liberamos el espacio del nombre del archivo de salida
    return (0);
}


